﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toolkit.Web.Infastructure;
using Toolkit.Web.ExtensionMethods;
using Toolkit.Web.Helpers;
using System.Web;

namespace Toolkit.Web.ViewComponents.DataGrid
{
    public sealed class DataGridHtmlBuilder<T> : ViewComponentHtmlBuilderBase where T : class
    {
        public DataGridHtmlBuilder(DataGrid<T> dataGrid)
            : base(dataGrid)
        {
            if (dataGrid == null)
            {
                throw new ArgumentNullException("dataGrid");
            }

            _dataGrid = dataGrid;
            CssClassNames = new ComponentCssClassNames();
            _additionalNonScrollingTableWidth = 10000;
        }

        private DataGrid<T> _dataGrid
        {
            get;
            set;
        }

        private int _additionalNonScrollingTableWidth;

        public ComponentCssClassNames CssClassNames
        {
            get;
            private set;
        }

        public bool ContainsColumnsWithDefinedFilters
        {
            get;
            private set;
        }

        public IList<DataGridColumn<T>> ColumnsInOrder
        {
            get;
            private set;
        }

        public IList<DataGridColumn<T>> VisibleColumnsInOrder
        {
            get;
            private set;
        }

        public IList<DataGridColumn<T>> ColumnsToRenderInOrder
        {
            get;
            private set;
        }

        public int TableWidth
        {
            get;
            private set;
        }

        public int NumberOfResults
        {
            get;
            private set;
        }

        public override IHtmlNode Html()
        {
            NumberOfResults = _dataGrid.DataSource != null ? _dataGrid.DataSource.Count() : 0;
            ContainsColumnsWithDefinedFilters = _dataGrid.ContainsColumnsWithDefinedFilters();

            // Note: It is important to keep the exact same column order that is initialy rendered on the page when in RowsOnly render mode.
            ColumnsInOrder = _dataGrid.GetOrderedColumns();

            VisibleColumnsInOrder = ColumnsInOrder.Where(column => column.Settings.IsVisible).ToList();
            TableWidth = VisibleColumnsInOrder.Sum(column => column.Settings.Width.HasValue ? column.Settings.Width.Value : _dataGrid.Settings.DefaultColumnWidth);
            ColumnsToRenderInOrder = ColumnsInOrder.Where(column => _dataGrid.Settings.RenderHiddenColumns || column.Settings.IsVisible).ToList();

            if (_dataGrid.Settings.RenderMode == DataGridRenderMode.ContentOnly)
            {
                _dataGrid.Identifier = null;
                return ContentOnlyRenderModeMarkup();
            }
            else if (_dataGrid.Settings.RenderMode == DataGridRenderMode.RowsOnly)
            {
                _dataGrid.Identifier = null;
                return RowsOnlyRenderModeMarkup();
            }
            else
            {
                return DataGridMarkup();
            }
        }

        public IHtmlNode DataGridMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.MergeAttributes(_dataGrid.HtmlAttributes, true);
            htmlTag.AddClass(string.Format("{0}_{1}", CssClassNames.ComponentWrapper, _dataGrid.Identifier));
            htmlTag.AddClass(CssClassNames.ComponentWrapper);

            // Inform client to initialize this data grid on render.
            htmlTag.MergeAttribute("data-initialize", bool.TrueString, true);
            htmlTag.AddClass(CssClassNames.Loading);

            // Header class name.
            if (_dataGrid.HeaderTemplate != null && _dataGrid.HeaderTemplate.HasValue())
            {
                htmlTag.AddClass(CssClassNames.ComponentWrapperWithHeader);
            }

            AppendContentMarkup(htmlTag);

            // TODO: Insert javascript.

            return htmlTag;
        }

        public IHtmlNode ContentOnlyRenderModeMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            AppendContentMarkup(htmlTag);
            return htmlTag;
        }

        public IHtmlNode RowsOnlyRenderModeMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");

            if (_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                AppendVirtualizedScrollingMarkup(htmlTag);
            }
            else
            {
                AppendTableBodyRowMarkup(htmlTag);
            }

            return htmlTag;
        }

        public void AppendContentMarkup(IHtmlNode htmlTag)
        {
            htmlTag.Append(RelativePositionedMarkup());

            // Virtualized scrolling markup.
            if (_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                htmlTag.Append(VirtualizedScrollingMarkupWrapper());
            }

            // Column context menu.
            if (_dataGrid.Settings.EnableColumnMenus && _dataGrid.Menu != null)
            {
                htmlTag.Append(MenuMarkup(_dataGrid.Menu));
            }

            // Column filters.
            if (_dataGrid.HasFilterableColumns())
            {
                htmlTag.Append(ColumnFilterCollectionMarkup());
            }

            // Additional markup.
            if (_dataGrid.AdditionalHiddenTemplates != null && _dataGrid.AdditionalHiddenTemplates.Count > 0)
            {
                htmlTag.Append(AdditionalHiddenMarkup());
            }

            // Error template.
            if (_dataGrid.ErrorLoadingDataTemplate != null && _dataGrid.ErrorLoadingDataTemplate.HasValue())
            {
                htmlTag.Append(ErrorLoadingDataMarkup());
            }

            // No results template (only apply if using virtualized scrolling).
            if (_dataGrid.Settings.ApplyVirtualizedScrolling && _dataGrid.NoResultsTemplate != null && _dataGrid.NoResultsTemplate.HasValue())
            {
                IHtmlNode noResultsTag = new HtmlNode("div");
                noResultsTag.AddClass(CssClassNames.EmptyDataSource);
                noResultsTag.AddStyle("display", "none");
                noResultsTag.ApplyTemplate(_dataGrid.NoResultsTemplate);
                htmlTag.Append(noResultsTag);
            }

            htmlTag.Append(ClientModel());
        }

        public IHtmlNode RelativePositionedMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.RelativeWrapper);

            // Note: For IE7 and below, elements need to have a position to be able to have an opacity applied to them. Also makes it easier to 
            // position child elements.
            htmlTag.AddStyle("position", "relative");

            // Render loading markup first (becomes important when rendering very large amounts of data).
            htmlTag.Append(OverlayMarkup());

            // Optional header.
            if (_dataGrid.HeaderTemplate != null && _dataGrid.HeaderTemplate.HasValue())
            {
                htmlTag.Append(HeaderMarkup());
            }

            // Content.
            htmlTag.Append(ContentMarkup());

            // Optional footer.
            if (_dataGrid.FooterTemplate != null && _dataGrid.FooterTemplate.HasValue())
            {
                htmlTag.Append(FooterMarkup());
            }

            // Vertical resize handle.
            if (_dataGrid.Settings.EnableViewPortResizing)
            {
                htmlTag.Append(VerticalResizeMarkup());
            }

            return htmlTag;
        }

        public IHtmlNode VirtualizedScrollingMarkupWrapper()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.VirtualizedScrollingCacheWrapper);
            htmlTag.AddStyle("display", "none");
            AppendVirtualizedScrollingMarkup(htmlTag);
            return htmlTag;
        }

        public void AppendVirtualizedScrollingMarkup(IHtmlNode htmlTag)
        {
            if (_dataGrid.DataSource == null)
            {
                // Load data rows after page load through ajax.
                // Do nothing.
            }
            else
            {
                // Write all rows (and child rows if applicable).
                int index = _dataGrid.Settings.RecordStartIndex;
                
                // Verify only the requested page size is retrieved.
                IEnumerable<T> source = _dataGrid.DataSource;
                if (_dataGrid.Settings.RecordSetSize.HasValue)
                {
                    source = source.Take(_dataGrid.Settings.RecordSetSize.Value);
                }

                foreach (T dataItem in source)
                {
                    // Note: script tags with unknown type attributes are ignored by the browser during DOM parsing.
                    IHtmlNode script = new HtmlNode("script");
                    script.MergeAttribute("type", "text/x-datagrid-row", true);
                    script.MergeAttribute("data-row-index", index.ToString(), true);

                    // Row tag.
                    DataGridRow<T> row = new DataGridRow<T>(_dataGrid, dataItem, index);

                    if (_dataGrid.RowAction != null)
                    {
                        _dataGrid.RowAction(row);
                    }

                    script.MergeAttribute("data-row-key", row.SetOrDefaultKey, true);

                    script.InnerHtml = TableRowMarkup(row).ToString();
                    htmlTag.Append(script);

                    index += 1;
                }
            }
        }

        public IHtmlNode OverlayMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.Overlay);

            // Initially show component with overlay.
            htmlTag.AddStyle("display", "block");

            htmlTag.AddStyle("position", "absolute");
            htmlTag.AddStyle("width", "100%");
            htmlTag.AddStyle("height", "100%");
            htmlTag.AddStyle("top", "0");
            htmlTag.AddStyle("bottom", "0");
            htmlTag.AddStyle("left", "0");
            htmlTag.AddStyle("right", "0");

            if (_dataGrid.LoadingOverlayTemplate != null && _dataGrid.LoadingOverlayTemplate.HasValue())
            {
                htmlTag.ApplyTemplate(_dataGrid.LoadingOverlayTemplate);
            }

            return htmlTag;
        }

        public IHtmlNode HeaderMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.Header);

            if (_dataGrid.HeaderTemplate != null && _dataGrid.HeaderTemplate.HasValue())
            {
                htmlTag.ApplyTemplate(_dataGrid.HeaderTemplate, _dataGrid);
            }

            return htmlTag;
        }

        public IHtmlNode ContentMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.Content);
            htmlTag.AddStyle("position", "relative");

            htmlTag.Append(TableHeaderWrapperMarkup());
            htmlTag.Append(TableBodyWrapperMarkup());

            // If column footer templates exist, render footer row.
            if (_dataGrid.Columns.FirstOrDefault(col => col.FooterTemplate != null) != null)
            {
                htmlTag.Append(TableFooterWrapperMarkup());
            }

            // Virtual scrolling helper.
            if (_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                htmlTag.Append(VirtualScrollingHelperMarkup());
            }

            return htmlTag;
        }

        public IHtmlNode FooterMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.Footer);
            htmlTag.AddStyle("position", "relative");

            if (_dataGrid.FooterTemplate != null && _dataGrid.FooterTemplate.HasValue())
            {
                htmlTag.ApplyTemplate(_dataGrid.FooterTemplate, _dataGrid);
            }

            return htmlTag;
        }

        public IHtmlNode VerticalResizeMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.VerticalResizeHandle);
            htmlTag.AddStyle("min-height", "1px");
            htmlTag.Append(new HtmlNode("span"));
            return htmlTag;
        }

        public IHtmlNode MenuMarkup(DataGridMenu<T> menu)
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.MergeAttributes(menu.HtmlAttributes, true);
            htmlTag.AddClass(CssClassNames.Menu);
            htmlTag.AddStyle("display", "none");

            if (menu.UniversalSectionTemplates != null)
            {
                foreach (HtmlTemplate<IReadOnlyDataGrid<T>> template in menu.UniversalSectionTemplates)
                {
                    IHtmlNode sectionTag = new HtmlNode("div");
                    sectionTag.AddClass(CssClassNames.MenuSection);
                    sectionTag.ApplyTemplate(template, _dataGrid);
                    htmlTag.Append(sectionTag);
                }
            }

            if (menu.ColumnSpecificSectionTemplates != null)
            {
                foreach (KeyValuePair<Guid, IList<HtmlTemplate<IReadOnlyDataGridColumn<T>>>> keyValue in menu.ColumnSpecificSectionTemplates)
                {
                    DataGridColumn<T> columnInstance = _dataGrid.Columns.First(column => column.Identifier == keyValue.Key);
                    foreach (HtmlTemplate<IReadOnlyDataGridColumn<T>> template in keyValue.Value)
                    {
                        IHtmlNode sectionTag = new HtmlNode("div");
                        sectionTag.AddClass(CssClassNames.MenuSection);
                        sectionTag.MergeAttribute("data-col-key", columnInstance.Settings.Key, true);
                        sectionTag.ApplyTemplate(template, columnInstance);
                        htmlTag.Append(sectionTag);
                    }
                }
            }

            return htmlTag;
        }

        public IHtmlNode AdditionalHiddenMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.AdditionalHiddenMarkup);
            htmlTag.AddStyle("display", "none");

            if (_dataGrid.AdditionalHiddenTemplates != null)
            {
                foreach (HtmlTemplate template in _dataGrid.AdditionalHiddenTemplates.Where(t => t.HasValue()))
                {
                    IHtmlNode hiddenTag = new HtmlNode("div");
                    hiddenTag.ApplyTemplate(template);
                    htmlTag.Append(hiddenTag);
                }
            }

            return htmlTag;
        }

        public IHtmlNode ErrorLoadingDataMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.ErrorLoadingData);
            htmlTag.AddStyle("display", "none");
            htmlTag.ApplyTemplate(_dataGrid.ErrorLoadingDataTemplate);
            return htmlTag;
        }

        public IHtmlNode TableHeaderWrapperMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableHeaderWrapper);

            // Not scrollable.
            htmlTag.AddStyle("overflow", "hidden");

            // Need to position this as relative for IE7 to handle scrolling collumns.
            htmlTag.AddStyle("position", "relative");

            // Insert table header.
            htmlTag.Append(TableHeaderMarkup());

            // Insert table filter.
            if (ContainsColumnsWithDefinedFilters)
            {
                htmlTag.Append(TableFilterMarkup());
            }

            return htmlTag;
        }

        public IHtmlNode TableHeaderMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableHeader);
            htmlTag.AddStyle("display", "none");
            htmlTag.AddStyle("width", (TableWidth + _additionalNonScrollingTableWidth) + "px");

            if (_dataGrid.Settings.IncludeInSelectionByDefault)
            {
                htmlTag.AddClass(CssClassNames.IncludeInSelectionByDefault);
            }

            if ((_dataGrid.Settings.IncludeInSelectionByDefault && _dataGrid.Settings.ExcludeFromSelection != null && _dataGrid.Settings.ExcludeFromSelection.Count > 0) ||
                (!_dataGrid.Settings.IncludeInSelectionByDefault && _dataGrid.Settings.IncludeInSelection != null && _dataGrid.Settings.IncludeInSelection.Count > 0))
            {
                htmlTag.AddClass(CssClassNames.MixedSelection);
            }

            IHtmlNode rowTag = new HtmlNode("div");
            rowTag.AddClass(CssClassNames.TableRow);

            foreach (DataGridColumn<T> column in ColumnsToRenderInOrder)
            {
                IHtmlNode cellTag = new HtmlNode("div");
                cellTag.MergeAttribute("data-col-key", column.Settings.Key, true);
                cellTag.AddClass(CssClassNames.TableCell);
                cellTag.AddStyle("height", _dataGrid.Settings.DefaultOrSetHeaderRowHeight + "px");

                // Title attribute.
                if (!HtmlStringExtensions.IsNullOrEmpty(column.Label))
                {
                    cellTag.MergeAttribute("title", column.Label.ToHtmlString(), false);
                }

                // Column styles.
                SetColumnCellStyles(cellTag, column);

                // If column is resizable, give css the ability to style resizable column cells as needed.
                if (_dataGrid.Settings.EnableColumnResizing && column.Settings.EnableResizing)
                {
                    cellTag.AddClass(CssClassNames.ResizableColumn);
                }

                // If column is sortable, give css the ability to style sortable column cells as needed.
                if (_dataGrid.Settings.EnableColumnSorting && column.Settings.EnableSorting)
                {
                    cellTag.AddClass(CssClassNames.SortableColumn);
                }

                // If column menu is enabled, give css ability to style column cells as needed.
                if (_dataGrid.Settings.EnableColumnMenus && column.Settings.EnableMenu)
                {
                    cellTag.AddClass(CssClassNames.ColumnWithMenu);
                }

                // A column may not be sortable but could be actively sorted. Apply class names as needed to allow css
                // to control the style of currently sorted column cells.
                if (_dataGrid.Settings.IsSortByColumn(column.Settings))
                {
                    cellTag.AddClass(CssClassNames.ActiveSortableColumn);

                    if (_dataGrid.Settings.IsSortedInDescendingOrder)
                    {
                        cellTag.AddClass(CssClassNames.SortedInDescendingDirection);
                    }
                    else
                    {
                        cellTag.AddClass(CssClassNames.SortedInAscendingDirection);
                    }
                }

                IHtmlNode innerCellTag = new HtmlNode("div");
                innerCellTag.MergeAttributes(column.HeaderHtmlAttributes, true);
                innerCellTag.AddClass(CssClassNames.TableInnerCell);

                // Insert column resize handle if enabled.
                if (_dataGrid.Settings.EnableColumnResizing && column.Settings.EnableResizing)
                {
                    IHtmlNode resizeHandle = new HtmlNode("div");
                    resizeHandle.AddClass(CssClassNames.ColumnResizeHandle);

                    // Add inner markup tag for possible icon.
                    IHtmlNode iconTag = new HtmlNode("span");
                    resizeHandle.Append(iconTag);
                    innerCellTag.Append(resizeHandle);
                }
                
                // Insert column dropdown trigger if enabled.
                if (_dataGrid.Settings.EnableColumnMenus && column.Settings.EnableMenu)
                {
                    IHtmlNode menuDropdownTrigger = new HtmlNode("div");
                    menuDropdownTrigger.AddClass(CssClassNames.ColumnMenuHandle);

                    // Add inner markup tag for possible icon.
                    IHtmlNode iconTag = new HtmlNode("span");
                    menuDropdownTrigger.Append(iconTag);
                    innerCellTag.Append(menuDropdownTrigger);
                }

                // Insert title content markup.
                IHtmlNode titleWrapper = new HtmlNode("div");
                titleWrapper.AddClass(CssClassNames.ColumnTitle);
                titleWrapper.AddStyle("overflow", "hidden");
                titleWrapper.AddStyle("text-overflow", "ellipsis");
                titleWrapper.AddStyle("white-space", "nowrap");

                // Place title in a child element to allow for icons to the right or left of the title (through css).
                IHtmlNode innerTitleTag = new HtmlNode("span");
                if (column.HeaderTemplate != null && column.HeaderTemplate.HasValue())
                {
                    innerTitleTag.ApplyTemplate(column.HeaderTemplate);
                }
                else if (column.DataGrid.DefaultColumnHeaderTemplate != null && column.DataGrid.DefaultColumnHeaderTemplate.HasValue())
                {
                    innerTitleTag.ApplyTemplate(column.DataGrid.DefaultColumnHeaderTemplate);
                }

                titleWrapper.Append(innerTitleTag);
                innerCellTag.Append(titleWrapper);

                // Insert optional sortable icon elements which can be implemented through css. Put this last to allow it 
                // to go to the left and right of the header template.
                if ((_dataGrid.Settings.EnableColumnSorting && column.Settings.EnableSorting) || _dataGrid.Settings.IsSortByColumn(column.Settings))
                {
                    IHtmlNode sortableIcon = new HtmlNode("div");
                    sortableIcon.AddClass(CssClassNames.ColumnSortHandle);

                    IHtmlNode innerSpanTag = new HtmlNode("span");
                    sortableIcon.Append(innerSpanTag);
                    innerCellTag.Append(sortableIcon);
                }

                cellTag.Append(innerCellTag);
                rowTag.Append(cellTag);
            }

            htmlTag.Append(rowTag);
            return htmlTag;
        }

        public IHtmlNode TableFilterMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableFilter);
            htmlTag.AddStyle("display", "none");
            htmlTag.AddStyle("width", (TableWidth + _additionalNonScrollingTableWidth) + "px");

            IHtmlNode rowTag = new HtmlNode("div");
            rowTag.AddClass(CssClassNames.TableRow);

            foreach (DataGridColumn<T> column in ColumnsToRenderInOrder)
            {
                IHtmlNode cellTag = new HtmlNode("div");
                cellTag.AddClass(CssClassNames.TableCell);
                cellTag.MergeAttribute("data-col-key", column.Settings.Key, true);
                cellTag.AddStyle("height", _dataGrid.Settings.DefaultOrSetFilterRowHeight + "px");

                // Column styles.
                SetColumnCellStyles(cellTag, column);

                // A column may not be sortable but could be actively sorted. Apply class names as needed to allow css
                // to control the style of currently sorted column cells.
                if (_dataGrid.Settings.IsSortByColumn(column.Settings))
                {
                    cellTag.AddClass(CssClassNames.ActiveSortableColumn);
                }

                IHtmlNode innerCellTag = new HtmlNode("div");
                innerCellTag.MergeAttributes(column.HeaderHtmlAttributes, true);
                innerCellTag.AddClass(CssClassNames.TableInnerCell);

                if (column.IsFilterable)
                {
                    IHtmlNode summaryTag = new HtmlNode("div");
                    summaryTag.AddClass("vc-dg-f-summary");
                    if (column.FilterSummaryTemplate != null && column.FilterSummaryTemplate.HasValue())
                    {
                        summaryTag.ApplyTemplate(column.FilterSummaryTemplate, column);
                    }
                    innerCellTag.Append(summaryTag);
                }

                cellTag.Append(innerCellTag);
                rowTag.Append(cellTag);
            }

            htmlTag.Append(rowTag);
            return htmlTag;
        }

        public IHtmlNode ColumnFilterCollectionMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass("vc-dg-filter");
            htmlTag.AddStyle("display", "none");

            IHtmlNode clearTag = new HtmlNode("div");
            clearTag.AddClass("vc-dg-f-clear");
            IHtmlNode clearIconTag = new HtmlNode("span");
            clearIconTag.AddClass("vc-icon");
            clearIconTag.MergeAttribute("title", "Clear Filter", true);
            clearTag.Append(clearIconTag);
            IHtmlNode clearTextTag = new HtmlNode("span");
            clearTextTag.AddClass("vc-text");
            clearTextTag.MergeAttribute("title", "Clear Filter", true);
            clearTextTag.InnerHtml = HttpUtility.HtmlEncode("Clear Filter");
            clearTag.Append(clearTextTag);
            htmlTag.Append(clearTag);

            foreach (DataGridColumn<T> column in ColumnsToRenderInOrder.Where(c => c.IsFilterable))
            {
                htmlTag.Append(ColumnFilterMarkup(column));
            }

            IHtmlNode applyTag = new HtmlNode("div");
            applyTag.AddClass("vc-dg-f-apply");
            IHtmlNode applyIconTag = new HtmlNode("span");
            applyIconTag.AddClass("vc-icon");
            applyIconTag.MergeAttribute("title", "Apply Filter", true);
            applyTag.Append(applyIconTag);
            IHtmlNode applyTextTag = new HtmlNode("span");
            applyTextTag.AddClass("vc-text");
            applyTextTag.MergeAttribute("title", "Apply Filter", true);
            applyTextTag.InnerHtml = HttpUtility.HtmlEncode("Apply Filter");
            applyTag.Append(applyTextTag);
            htmlTag.Append(applyTag);

            return htmlTag;
        }

        public IHtmlNode ColumnFilterMarkup(DataGridColumn<T> column)
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass("vc-dg-f-col");
            htmlTag.MergeAttribute("data-col-key", column.Settings.Key, true);

            IHtmlNode optionsTag = new HtmlNode("div");
            optionsTag.AddClass("vc-dg-f-options");

            int index = 0;
            foreach (DataGridColumnFilter filter in column.Filters)
            {
                if (index > 0)
                {
                    IHtmlNode groupOperatorTag = new HtmlNode("div");
                    groupOperatorTag.AddClass("vc-dg-f-group");
                    groupOperatorTag.InnerHtml = @"<span class=""vc-text"">AND</span>";
                    optionsTag.Append(groupOperatorTag);
                }

                IHtmlNode groupTag = new HtmlNode("div");
                groupTag.AddClass("vc-dg-f-group");
                groupTag.AddClass("vc-dg-f-" + filter.Type.ToString().ToLower());

                if (filter.Type == DataGridColumnFilterType.Custom && filter.CustomFilterTemplate != null && filter.CustomFilterTemplate.HasValue())
                {
                    groupTag.ApplyTemplate(filter.CustomFilterTemplate, filter);
                }
                else
                {
                    groupTag.InnerHtml = column.FilterHelper.DefaultFilter(filter, index).ToString();
                }

                optionsTag.Append(groupTag);
                index += 1;
            }

            htmlTag.Append(optionsTag);

            return htmlTag;
        }

        public IHtmlNode TableBodyWrapperMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableBodyWrapper);

            // If virtual scrolling is not enabled, apply initial load styles.
            if (!_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                // Scrolling styles.
                htmlTag.AddStyle("overflow-x", "auto");

                if (_dataGrid.Settings.MaximumViewPortHeight.HasValue || _dataGrid.Settings.EnableViewPortResizing)
                {
                    htmlTag.AddStyle("overflow-y", "scroll");
                }
                else
                {
                    htmlTag.AddStyle("overflow-y", "auto");
                }
            }

            // Need to position this as relative for IE7 to handle scrolling collumns.
            htmlTag.AddStyle("position", "relative");

            // Set maximum viewport height if applicable.
            if (_dataGrid.Settings.MaximumViewPortHeight.HasValue || _dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                htmlTag.AddStyle("max-height", _dataGrid.Settings.DefaultOrSetMaximumViewPortHeight + "px");
            }

            // Set minimum viewport height.
            htmlTag.AddStyle("min-height", _dataGrid.Settings.MinimumViewPortHeight + "px");

            // Insert table body.
            htmlTag.Append(TableBodyMarkup());

            return htmlTag;
        }

        public IHtmlNode VirtualScrollingHelperMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.VirtualizedScrollingHelper);
            htmlTag.AddStyle("display", "none");
            htmlTag.AddStyle("position", "absolute");
            return htmlTag;
        }

        public IHtmlNode TableBodyMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableBody);
            htmlTag.AddStyle("display", "none");
            htmlTag.AddStyle("width", TableWidth + "px");

            if (_dataGrid.Settings.IncludeInSelectionByDefault)
            {
                htmlTag.AddClass(CssClassNames.IncludeInSelectionByDefault);
            }

            if (_dataGrid.DataSource == null)
            {
                // Load data rows after page load through ajax.
                // Do nothing.
            }
            else
            {
                AppendTableBodyRowMarkup(htmlTag);
            }

            return htmlTag;
        }

        public void AppendTableBodyRowMarkup(IHtmlNode htmlTag)
        {
            if (_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                // Load data rows after page load through javascript.
                // Do nothing.
            }
            else if (_dataGrid.DataSource == null || NumberOfResults == 0)
            {
                // No results found. Insert empty desplay template.
                IHtmlNode emptyDataSourceTag = new HtmlNode("div");
                emptyDataSourceTag.AddClass(CssClassNames.EmptyDataSource);

                if (_dataGrid.NoResultsTemplate != null && _dataGrid.NoResultsTemplate.HasValue())
                {
                    emptyDataSourceTag.ApplyTemplate(_dataGrid.NoResultsTemplate);
                }

                htmlTag.Append(emptyDataSourceTag);
            }
            else
            {
                // Write all rows (and child rows if applicable).
                int index = _dataGrid.Settings.RecordStartIndex;

                // Verify only the requested page size is retrieved.
                IEnumerable<T> source = _dataGrid.DataSource;
                if (_dataGrid.Settings.RecordSetSize.HasValue)
                {
                    source = source.Take(_dataGrid.Settings.RecordSetSize.Value);
                }

                foreach (T dataItem in source)
                {
                    // Row tag.
                    DataGridRow<T> row = new DataGridRow<T>(_dataGrid, dataItem, index);

                    if (_dataGrid.RowAction != null)
                    {
                        _dataGrid.RowAction(row);
                    }

                    htmlTag.Append(TableRowMarkup(row));

                    // Child row tag.
                    if (_dataGrid.ChildRowAction != null)
                    {
                        DataGridChildRow<T> childRow = new DataGridChildRow<T>(_dataGrid, dataItem, index);
                        _dataGrid.ChildRowAction(childRow);

                        if (childRow.Conditional == null || childRow.Conditional(dataItem))
                        {
                            htmlTag.Append(ChildTableRowMarkup(childRow));
                        }
                    }

                    index += 1;
                }
            }
        }

        public IHtmlNode TableRowMarkup(DataGridRow<T> row)
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.MergeAttributes(row.HtmlAttributes, true);
            htmlTag.AddClass(CssClassNames.TableRow);

            // Row key.
            htmlTag.MergeAttribute("data-row-key", row.SetOrDefaultKey, true);

            bool isSelected = row.IsSelected();

            // Is alternate (even or odd) index.
            if (row.IsAlternate)
            {
                htmlTag.AddClass(CssClassNames.AlternateTableRow);
            }

            // Is row selected.
            if (!_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                if (isSelected && !_dataGrid.Settings.IncludeInSelectionByDefault)
                {
                    htmlTag.AddClass(CssClassNames.SelectedTableRow);
                }
                else if (!isSelected && _dataGrid.Settings.IncludeInSelectionByDefault)
                {
                    htmlTag.AddClass(CssClassNames.UnselectedTableRow);
                }
            }

            // Is row active.
            if (!string.IsNullOrEmpty(row.DataGrid.Settings.ActiveRowKey) && row.DataGrid.Settings.ActiveRowKey.IsCaseInsensitiveEqual(row.Key))
            {
                htmlTag.AddClass(CssClassNames.ActiveTableRow);
            }

            // Columns.
            foreach (DataGridColumn<T> column in ColumnsToRenderInOrder)
            {
                // Cell tag.
                DataGridCell<T> cell = new DataGridCell<T>(column, row.DataItem, row.Index);

                if (row.DataGrid.CellAction != null)
                {
                    row.DataGrid.CellAction(cell);
                }

                htmlTag.Append(TableBodyCellMarkup(cell));
            }

            return htmlTag;
        }

        public IHtmlNode ChildTableRowMarkup(DataGridChildRow<T> childRow)
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.ChildTableRow);
            htmlTag.AddStyle("display", "none");

            // Row styles
            if (_dataGrid.Settings.ApplyVirtualizedScrolling)
            {
                htmlTag.AddStyle("height", _dataGrid.Settings.DefaultOrSetChildRowHeight + "px");
                htmlTag.AddStyle("overflow", "scroll");
            }
            else if (_dataGrid.Settings.ChildRowHeight.HasValue)
            {
                htmlTag.AddStyle("height", _dataGrid.Settings.ChildRowHeight.Value + "px");
                htmlTag.AddStyle("overflow", "scroll");
            }

            // Is alternate (even or odd) index.
            if (childRow.IsAlternate)
            {
                htmlTag.AddClass(CssClassNames.AlternateTableRow);
            }

            // Column.
            IHtmlNode cellTag = new HtmlNode("div");
            cellTag.AddClass(CssClassNames.TableCell);
            cellTag.AddStyle("width", TableWidth + "px");

            // Inner cell content.
            IHtmlNode innerCellTag = new HtmlNode("div");
            innerCellTag.MergeAttributes(childRow.HtmlAttributes, true);
            innerCellTag.AddClass(CssClassNames.TableInnerCell);
            innerCellTag.ApplyTemplate(childRow.Template, childRow.DataItem);

            cellTag.Append(innerCellTag);
            htmlTag.Append(cellTag);

            return htmlTag;
        }

        public IHtmlNode TableBodyCellMarkup(DataGridCell<T> cell)
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.MergeAttribute("data-col-key", cell.Column.Settings.Key, true);
            htmlTag.AddClass(CssClassNames.TableCell);

            // Column styles.
            SetColumnCellStyles(htmlTag, cell.Column);

            // Height.
            htmlTag.AddStyle("height", cell.DataGrid.Settings.DefaultOrSetBodyRowHeight + "px");

            // A column may not be sortable but could be actively sorted. Apply class names as needed to allow css
            // to control the style of currently sorted column cells.
            if (_dataGrid.Settings.IsSortByColumn(cell.Column.Settings))
            {
                htmlTag.AddClass(CssClassNames.ActiveSortableColumn);
            }

            // Inner cell content.
            IHtmlNode innerCellTag = new HtmlNode("div");

            // Html attributes from column.
            innerCellTag.MergeAttributes(cell.Column.HtmlAttributes, true);

            // Html attributes from cell.
            innerCellTag.MergeAttributes(cell.HtmlAttributes, true);
            innerCellTag.AddClass(CssClassNames.TableInnerCell);

            // Horizontal alignment.
            if (!string.IsNullOrEmpty(cell.Column.Settings.HorizontalAlignment))
            {
                innerCellTag.AddStyle("text-align", cell.Column.Settings.HorizontalAlignment);
            }
            
            // Word wrap.
            if (cell.Column.Settings.EnableWordWrap)
            {
                innerCellTag.AddStyle("white-space", "normal");
            }

            // Template.
            if (cell.Column.Template != null && cell.Column.Template.HasValue())
            {
                innerCellTag.ApplyTemplate(cell.Column.Template, cell.DataItem);
            }
            else if (cell.DataGrid.DefaultColumnBodyTemplate != null && cell.DataGrid.DefaultColumnBodyTemplate.HasValue())
            {
                innerCellTag.ApplyTemplate(cell.DataGrid.DefaultColumnBodyTemplate, cell.DataItem);
            }

            htmlTag.Append(innerCellTag);

            // Editor tag (hidden on page load).
            if (cell.Column.Settings.EnableEditing)
            {
                IHtmlNode editorTag = new HtmlNode("div");
                editorTag.AddClass(CssClassNames.TableCellEditor);
                editorTag.AddStyle("display", "none");

                if (cell.Column.EditorTemplate != null && cell.Column.EditorTemplate.HasValue())
                {
                    editorTag.ApplyTemplate(cell.Column.EditorTemplate, cell.DataItem);
                }
                else if (cell.DataGrid.DefaultColumnEditorTemplate != null && cell.DataGrid.DefaultColumnEditorTemplate.HasValue())
                {
                    editorTag.ApplyTemplate(cell.DataGrid.DefaultColumnEditorTemplate, cell.DataItem);
                }

                htmlTag.Append(editorTag);
            }

            return htmlTag;
        }

        public IHtmlNode TableFooterWrapperMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableFooterWrapper);

            // Not scrollable.
            htmlTag.AddStyle("overflow", "hidden");

            // Need to position this as relative for IE7 to handle scrolling collumns.
            htmlTag.AddStyle("position", "relative");

            // Insert table footer.
            htmlTag.Append(TableFooterMarkup());

            return htmlTag;
        }

        public IHtmlNode TableFooterMarkup()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.TableFooter);
            htmlTag.AddStyle("width", (TableWidth + _additionalNonScrollingTableWidth) + "px");
            htmlTag.AddStyle("display", "none");

            IHtmlNode rowTag = new HtmlNode("div");
            rowTag.AddClass(CssClassNames.TableRow);

            foreach (DataGridColumn<T> column in ColumnsToRenderInOrder)
            {
                IHtmlNode cellTag = new HtmlNode("div");
                cellTag.AddClass(CssClassNames.TableCell);
                cellTag.MergeAttribute("data-col-key", column.Settings.Key, true);
                cellTag.AddStyle("height", _dataGrid.Settings.DefaultOrSetFooterRowHeight + "px");

                // Column styles.
                SetColumnCellStyles(cellTag, column);

                // A column may not be sortable but could be actively sorted. Apply class names as needed to allow css
                // to control the style of currently sorted column cells.
                if (_dataGrid.Settings.IsSortByColumn(column.Settings))
                {
                    cellTag.AddClass(CssClassNames.ActiveSortableColumn);
                }

                IHtmlNode innerCellTag = new HtmlNode("div");
                innerCellTag.MergeAttributes(column.FooterHtmlAttributes, true);
                innerCellTag.AddClass(CssClassNames.TableInnerCell);

                if (column.FooterTemplate != null && column.FooterTemplate.HasValue())
                {
                    innerCellTag.ApplyTemplate(column.FooterTemplate);
                }
                else if (column.DataGrid.DefaultColumnFooterTemplate != null && column.DataGrid.DefaultColumnFooterTemplate.HasValue())
                {
                    innerCellTag.ApplyTemplate(column.DataGrid.DefaultColumnFooterTemplate);
                }

                cellTag.Append(innerCellTag);
                rowTag.Append(cellTag);
            }

            htmlTag.Append(rowTag);
            return htmlTag;
        }

        public IHtmlNode ClientModel()
        {
            IHtmlNode htmlTag = new HtmlNode("div");
            htmlTag.AddClass(CssClassNames.ClientModel);
            htmlTag.AddStyle("display", "none");

            ClientDataGridModel<T> model = new ClientDataGridModel<T>(_dataGrid);
            htmlTag.InnerHtml = JsonSerialization.Serialize(model);

            return htmlTag;
        }

        public void SetColumnCellStyles(IHtmlNode cellTag, DataGridColumn<T> column)
        {
            // Visibility.
            if (!column.Settings.IsVisible)
            {
                cellTag.AddStyle("display", "none");
            }

            // Width.
            if (column.Settings.Width.HasValue)
            {
                cellTag.AddStyle("width", column.Settings.Width.Value + "px");
            }
            else
            {
                cellTag.AddStyle("width", _dataGrid.Settings.DefaultColumnWidth + "px");
            }
        }

        public class ComponentCssClassNames
        {
            public readonly string ComponentWrapper = "vc-dg";
            public readonly string ComponentWrapperWithHeader = "vc-dg-withHeader";
            public readonly string Loading = "vc-dg-loading";
            public readonly string RelativeWrapper = "vc-dg-relative";
            public readonly string VirtualizedScrollingCacheWrapper = "vc-dg-vScrollCache";
            public readonly string VirtualizedScrollingHelper = "vc-dg-vScrollHelper";
            public readonly string Overlay = "vc-dg-overlay";
            public readonly string Header = "vc-dg-header";
            public readonly string Content = "vc-dg-content";
            public readonly string Footer = "vc-dg-footer";
            public readonly string VerticalResizeHandle = "vc-dg-vResizeHandle";
            public readonly string AdditionalHiddenMarkup = "vc-dg-extra";
            public readonly string Menu = "vc-dg-menu";
            public readonly string MenuSection = "vc-dg-menuSection";
            public readonly string TableHeaderWrapper = "vc-dg-thWrapper";
            public readonly string TableBodyWrapper = "vc-dg-tbWrapper";
            public readonly string TableFooterWrapper = "vc-dg-tfWrapper";
            public readonly string TableHeader = "vc-dg-tHeader";
            public readonly string TableFilter = "vc-dg-tFilter";
            public readonly string TableBody = "vc-dg-tBody";
            public readonly string TableFooter = "vc-dg-tFooter";
            public readonly string TableRow = "vc-dg-row";
            public readonly string AlternateTableRow = "vc-dg-altRow";
            public readonly string SelectedTableRow = "vc-dg-selRow";
            public readonly string UnselectedTableRow = "vc-dg-unselRow";
            public readonly string IncludeInSelectionByDefault = "vc-dg-selRowsByDefault";
            public readonly string MixedSelection = "vc-dg-mixedSelection";
            public readonly string ActiveTableRow = "vc-dg-actRow";
            public readonly string ChildTableRow = "vc-dg-childRow";
            public readonly string TableCell = "vc-dg-cell";
            public readonly string TableInnerCell = "vc-dg-iCell";
            public readonly string TableCellEditor = "vc-dg-eCell";
            public readonly string ColumnWithMenu = "vc-dg-withMenu";
            public readonly string ResizableColumn = "vc-dg-resizable";
            public readonly string SortableColumn = "vc-dg-sortable";
            public readonly string ActiveSortableColumn = "vc-dg-sorting";
            public readonly string SortedInDescendingDirection = "vc-dg-descending";
            public readonly string SortedInAscendingDirection = "vc-dg-ascending";
            public readonly string ColumnResizeHandle = "vc-dg-colResizeHandle";
            public readonly string ColumnMenuHandle = "vc-dg-colMenuHandle";
            public readonly string ColumnSortHandle = "vc-dg-colSortHandle";
            public readonly string ColumnTitle = "vc-dg-colTitle";
            public readonly string EmptyDataSource = "vc-dg-emptyDataSource";
            public readonly string ErrorLoadingData = "vc-dg-errorLoadingData";
            public readonly string ClientModel = "vc-dg-model";

            // Used outside of this class:

            public readonly string ResetSettings = "vc-dg-reset";
            public readonly string ColumnMenuItem = "vc-dg-menuItem";
            public readonly string ColumnSortMenuSection = "vc-dg-sortMenu";
            public readonly string ColumnVisibilityMenuSection = "vc-dg-visibilityMenu";
            public readonly string ChildRowToggleCell = "vc-dg-crToggleCell";
            public readonly string ChildRowToggle = "vc-dg-crToggle";
            public readonly string RowIndexCell = "vc-dg-indexCell";
            public readonly string CheckboxCell = "vc-dg-cbCell";
            public readonly string Checkbox = "vc-dg-checkbox";
        }
    }
}
